import PluginBase from "./PluginBase.js";
import {eventDetached, eventRotating} from "../lib/gadget/TransformRotateControl.js";
import NormalPointIndicator from "../lib/gadget/NormalPointIndicator.js";
import NormalPointVO from "../model/NormalPointVO.js";
import IsoLineEntity from "../lib/gadget/IsoLineEntity.js";
import {KeyCode, MouseButton} from "../lib/CommonConsts.js";
import {arrAsubB, removeArrayItem} from "../lib/util/ToolMethods.js";
import * as THREE from "../../../modules/three/three.module.js";
import IsoLineVO from "../model/IsoLineVO.js";

const _raycaster = new THREE.Raycaster()

export default class IsoLinePlugin extends PluginBase {
  /**
   * @type {World3d}
   */
  world3d
  /**
   * 已经标定好的局部切平面
   * @type {NormalPointIndicator[]}
   */
  normalPoints = [];
  /**
   * 已经标定好的等值线
   * @type {IsoLineEntity[]}
   */
  isoLineList = [];
  /**
   * @type {NormalPointIndicator|null}
   */
  _movingIndicator
  /**
   * 正在吸附的法向采样点
   * @type {NormalPointIndicator|null}
   */
  _adheringIndicator
  /**
   * @type {NormalPointIndicator|null}
   */
  _selectedIndicator
  /**
   * @type {NormalPointIndicator|null}
   */
  _usingIndicator
  /**
   * @type {IsoLineEntity|null}
   */
  currentIsoLine
  /**
   *
   * @returns {THREE.Mesh|null}
   */
  get convexMesh() {
    return this.world3d._surfaceGridPlugin.convexMesh
  }

  constructor(world3d) {
    super()
    this.world3d = world3d
    const store = world3d.store
    const tControls = this.world3d.transformRotateControl
    tControls.addEventListener(eventDetached.type, () => {
      if (tControls.object instanceof NormalPointIndicator) {
        tControls.setTranslateVisible(true)
      }
    })
    tControls.addEventListener(eventRotating.type, () => {
      if (tControls.object instanceof NormalPointIndicator) {
        tControls.object.updateVO()
        tControls.object.updateTip()
        const involvedLines = this.isoLineList.filter(it => it.points.includes(tControls.object))
        involvedLines.forEach(it => it.updateShape(tControls.object))
      }
    })
  }
  storageSave(obj) {
    obj.normalPoints = this.normalPoints.map(it => it.vo.toJsonObj())
    const isoLines = this.isoLineList.map(it => {
      it.updateVO()
      return it.vo.toJsonObj()
    })
    obj.isoLines = isoLines
  }
  storageLoad(jsonObj) {
    if (jsonObj.normalPoints) {
      jsonObj.normalPoints.forEach(normalPtJson => {
        const vo = new NormalPointVO().fromJsonObj(normalPtJson)
        const indicator = new NormalPointIndicator(vo)
        this.normalPoints.push(indicator)
        this.world3d.scene.add(indicator)
      })
    }
    if (jsonObj.isoLines) {
      const normalPts = this.normalPoints.map(it => it.vo)
      jsonObj.isoLines.forEach(isoJson => {
        const vo = new IsoLineVO().fromJsonObj(isoJson, normalPts)
        const isoLine = new IsoLineEntity(vo).setPoints(this.normalPoints)
        isoLine.createOrUpdateShape()
        this.isoLineList.push(isoLine)
        this.world3d.scene.add(isoLine)
      })
    }
  }
  changeIndicatorBottom(keyCode) {
    const indicator = this._movingIndicator || this._selectedIndicator
    if (!indicator) {
      return
    }
    switch (keyCode) {
      case KeyCode.Equal:
        indicator.bottomExpand()
        break
      case KeyCode.Minus:
        indicator.bottomShrink()
        break
      case KeyCode.Digit0:
        indicator.scaleReset()
        break
    }
  }
  /**
   * 新增等值线
   * @param {string} isoType
   * @see IsoEnum
   */
  createNewIsoLine(isoType) {
    const lineEntity = new IsoLineEntity(new IsoLineVO(isoType))
    this.currentIsoLine = lineEntity
    this.world3d.scene.add(lineEntity)
    this.isoLineList.push(lineEntity)
    if (this._usingIndicator) {
      lineEntity.insertPoint(this._usingIndicator)
    }
  }
  // 在激活当前等值线的最后一个采样点，开始新增航点，反转等值线的序列，允许首尾两端都能追加采样点
  startAddPointsToIsoLine() {
    if (!this._usingIndicator || !this.currentIsoLine) {
      return
    }
    const arr = this.currentIsoLine.points
    const index = arr.indexOf(this._usingIndicator)
    if (index !== arr.length - 1) {
      return
    }
    arr.reverse()
    this.currentIsoLine.showTips(this.world3d.scene, this.world3d.camera)
  }
  // 为已经存在的等值线添加采样点
  insertNormalPoint() {
    if (!this.currentIsoLine) {
      return
    }
    const arr = this.currentIsoLine.points
    const index = arr.indexOf(this._selectedIndicator)
    if (index === arr.length - 1 || index < 0) {
      // 不能在序列末尾插入
      return
    }
    const prev = arr[index]
    const next = arr[index + 1]
    const camera = this.world3d.camera
    _raycaster.near = camera.near
    _raycaster.far = camera.far
    const mid = prev.vo.position.clone().lerp(next.vo.position, 0.5)
    const midDir = prev.vo.normal.clone().add(next.vo.normal).normalize()
    _raycaster.ray.origin.copy(midDir).multiplyScalar(12).add(mid)
    _raycaster.ray.direction.copy(midDir).negate()
    const vo = new NormalPointVO()
    let hitWithConvex = false
    if (this.convexMesh) {
      const intersects = _raycaster.intersectObject(this.convexMesh, false)
      if (intersects.length) {
        vo.position.copy(intersects[0].point)
        vo.normal.copy(intersects[0].face.normal)
        hitWithConvex = true
      }
    }
    if (!hitWithConvex) {
      const tileIntersects = _raycaster.intersectObject(this.world3d.tiles.group, true)
      if (!tileIntersects.length) {
        console.log('插入采样点失败') // 极小概率
        return
      }
      vo.position.copy(tileIntersects[0].point)
      vo.normal.copy(tileIntersects[0].face.normal)
    }
    const indicator = new NormalPointIndicator(vo)
    this.normalPoints.push(indicator)
    this.world3d.scene.add(indicator)
    arr.splice(index + 1, 0, indicator)
    this.currentIsoLine.createOrUpdateShape()
  }
  setUsingIndicator() {
    if (this._usingIndicator) {
      this._usingIndicator.toggleSelect(false)
      this._usingIndicator.toggleActive(false)
      this._usingIndicator = null
    }
    if (!this._selectedIndicator) {
      return
    }
    this._usingIndicator = this._selectedIndicator
    this._usingIndicator.toggleActive(true, this.world3d.camera)
  }
  setNormalPointOrient() {
    if (!this._selectedIndicator) {
      return
    }
    const tControls = this.world3d.transformRotateControl
    if (tControls.object === this._selectedIndicator) {
      tControls.detach()
      return
    }
    tControls.attach(this._selectedIndicator)
    tControls.setRotateVisible(true)
    tControls.setTranslateVisible(false)
  }
  dropdownNormalPoint(event) {
    this.currentIsoLine.hideEndSegment()
    if (event.button === MouseButton.right) {
      // 取消新增 法向采样点
      this._movingIndicator.hideTip()
      this._movingIndicator.removeFromParent()
    } else {
      if (this._adheringIndicator) {
        this._movingIndicator.hideTip()
        this._movingIndicator.removeFromParent()
        this._adheringIndicator.showTip(this.world3d.scene, this.world3d.camera)
        this._adheringIndicator.updateTipByColor(this.currentIsoLine)
        this.currentIsoLine.insertPoint(this._adheringIndicator)
      } else {
        this.currentIsoLine.insertPoint(this._movingIndicator)
        this.normalPoints.push(this._movingIndicator)
      }
      this.currentIsoLine.createOrUpdateShape()
    }
    // this._movingIndicator.scaleReset()
    this._movingIndicator = null
  }
  dropdown4Move(event) {
    if (event.button === MouseButton.right || !this._adheringIndicator || !this._selectedIndicator) {
      return
    }
    const store = this.world3d.store
    const involvedLines = this.isoLineList.filter(it => it.points.includes(this._adheringIndicator))
    involvedLines.forEach(it => it.replacePoint(this._adheringIndicator, this._selectedIndicator))
    removeArrayItem(this.normalPoints, this._adheringIndicator)
    console.log('dropdown4Move')
    this._adheringIndicator.removeFromParent()
    this._adheringIndicator.hideTip()
    this._adheringIndicator = null
  }
  removeNormalPoint() {
    if (!this._selectedIndicator) {
      return
    }
    const store = this.world3d.store
    removeArrayItem(this.normalPoints, this._selectedIndicator)
    if (this._usingIndicator === this._selectedIndicator) {
      this._usingIndicator = null
    }
    const involvedLines = this.isoLineList.filter(it => it.points.includes(this._selectedIndicator))
    involvedLines.forEach(isoLineEntity => {
      removeArrayItem(isoLineEntity.points, this._selectedIndicator)
      isoLineEntity.createOrUpdateShape()
    })
    this.isoLineList = this.isoLineList.filter(it => it.points.length < 2)
    this._selectedIndicator.hideTip()
    this._selectedIndicator.removeFromParent()
    this._selectedIndicator = null
  }
  removeIsoLine() {
    if (!this.currentIsoLine) {
      return
    }
    const store = this.world3d.store
    this.currentIsoLine.removeFromParent()
    removeArrayItem(this.isoLineList, this.currentIsoLine)
    const pointsRemovable = []
    this.currentIsoLine.points.forEach(pt => {
      const otherIsoLine = this.isoLineList.find(isoLine => isoLine.points.includes(pt))
      if (!otherIsoLine) {
        pointsRemovable.push(pt)
      }
    })
    arrAsubB(this.normalPoints, pointsRemovable)
    pointsRemovable.forEach(it => {
      it.hideTip()
      it.removeFromParent()
    })
    this.currentIsoLine = null
  }
  _unSelect() {
    if (this._selectedIndicator) {
      this._selectedIndicator.toggleSelect(false)
      this._selectedIndicator = null
    }
  }
  pointerMove(event) {
    if (!this.currentIsoLine) {
      return
    }
    const world3d = this.world3d
    const store = this.world3d.store
    const raycaster = world3d.mouseRaycaster
    if (store.stateAddingNormalPoint && !this._movingIndicator) {
      const indicator = new NormalPointIndicator(new NormalPointVO())
      world3d.scene.add(indicator)
      this._movingIndicator = indicator
    }
    if (!this._movingIndicator && !store.stateMovingNormalPoint) {
      return
    }
    const pickingIndicator = this._selectedIndicator
    if (store.stateMovingNormalPoint && !pickingIndicator) {
      return
    }
    const normalPoints = this.normalPoints.filter(it => !this.currentIsoLine.points.includes(it))
    const pIntersects = raycaster.intersectObjects(normalPoints, false)
    const indicator = store.stateMovingNormalPoint ? pickingIndicator : this._movingIndicator
    const vo = indicator.vo
    if (this._adheringIndicator) {
      this._adheringIndicator.toggleAdhered(false)
    }
    if (pIntersects.length) {
      const adhered = pIntersects[0].object
      adhered.toggleAdhered(true)
      this._adheringIndicator = adhered
      vo.position.copy(adhered.vo.position)
      vo.normal.copy(adhered.vo.normal)
    } else {
      this._adheringIndicator = null
      let hitWithConvex = false
      if (this.convexMesh) {
        const intersects = raycaster.intersectObject(this.convexMesh, false)
        if (intersects.length) {
          vo.position.copy(intersects[0].point)
          vo.normal.copy(intersects[0].face.normal)
          hitWithConvex = true
        }
      }
      if (!hitWithConvex) {
        const tileIntersects = raycaster.intersectObject(world3d.tiles.group, true)
        if (tileIntersects.length) {
          vo.position.copy(tileIntersects[0].point)
          vo.normal.copy(tileIntersects[0].face.normal)
        }
      }
    }
    indicator.showTip(world3d.scene, world3d.camera)
    indicator.syncToVO()
    if (!this.currentIsoLine.points.length) {
      indicator.updateTip()
    } else {
      indicator.updateTipByColor(this.currentIsoLine)
    }
    if (store.stateMovingNormalPoint) {
      const involvedLines = this.isoLineList.filter(it => it.points.includes(indicator))
      involvedLines.forEach(it => it.updateShape(indicator))
    } else {
      this.currentIsoLine.updateEndSegment(this._movingIndicator)
    }
  }
  pointerUpMoved(event) {}
  pointerUpUnmoved(event) {
    const world3d = this.world3d
    const store = this.world3d.store
    const raycaster = world3d.mouseRaycaster
    if (store.stateAddingNormalPoint) {
      this.dropdownNormalPoint(event)
    } else if (store.stateMovingNormalPoint) {
      this.dropdown4Move(event)
    }
    if (event.button === MouseButton.right) {
      if (store.stateAddingNormalPoint || store.stateMovingNormalPoint) {
        store.freeState()
        if (this.currentIsoLine && this.currentIsoLine.points.length < 2) {
          this.removeIsoLine()
        }
      }
      return
    }
    if (store.stateMovingNormalPoint && this._selectedIndicator) {
      this._unSelect()
      return
    }
    if (!store.stateFree && !store.stateMovingNormalPoint) {
      return
    }
    if (this._selectedIndicator) {
      this._selectedIndicator.toggleSelect(false)
      this._selectedIndicator = null
    }
    let normalPoint
    const itemIntersects = raycaster.intersectObjects(this.normalPoints, false)
    if (itemIntersects.length) {
      normalPoint = itemIntersects[0].object
      normalPoint.toggleSelect(true)
      this._selectedIndicator = normalPoint
      return
    }
    if (this.currentIsoLine) {
      this.currentIsoLine.hideTips()
      this.currentIsoLine = null
    }
    const lines = this.isoLineList.map(it => it.lineSegments)
    const lineIntersects = raycaster.intersectObjects(lines, false)
    if (lineIntersects.length) {
      const index = lines.indexOf(lineIntersects[0].object)
      const lineEntity = this.isoLineList[index]
      lineEntity.showTips(world3d.scene, world3d.camera)
      this.currentIsoLine = lineEntity
    }
  }
}
